﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using VersionComparer.UI.ExtendedRichTextBox;
using VersionComparer.UI.ExtendedTreeView;
using VersionComparer.Base;

namespace VersionComparer
{
    public class ExtendedRichTextBox : RichTextBox
    {

        private class RtfChanges : IDisposable
        {
            ExtendedRichTextBox owner;
            RichTextBox textBox;
            int? selectionStart;
            int? selectionLength;

            public RtfChanges(ExtendedRichTextBox parent)
            {
                owner = parent;
            }
            RichTextBox Textbox
            {
                get
                {
                    if (textBox == null)
                    {
                        textBox = new RichTextBox();
                        textBox.Rtf = owner.Rtf;
                    }
                    return textBox;
                }
            }
            public void AddTextInSelection(string text)
            {
                var textBox = Textbox;
                string rtf = textBox.Rtf;
                textBox.Select(owner.SelectionStart, 0);
                textBox.SelectedText = text;
                selectionStart = owner.SelectionStart;
                selectionLength = text.Length;
            }

            public  void ChangeLineColor( int lineIndex , Color newColor)
            {
                var textBox = Textbox;
                textBox.Select(owner.GetFirstCharIndexFromLine(lineIndex), owner.Lines[lineIndex].Length);
                if (textBox.SelectionColor != newColor)
                    textBox.SelectionColor = newColor;
                
            }

            public void Apply()
            {
                if (textBox != null)
                {
                    if (selectionStart == null)
                        selectionStart = owner.SelectionStart;
                    if (selectionLength== null)
                        selectionLength = owner.SelectionLength;
                    owner.Rtf = textBox.Rtf;
                }

                if (selectionStart != null)
                    owner.SelectionStart = selectionStart.Value;

                if (selectionLength!= null)
                    owner.SelectionLength = selectionLength.Value;
            }

            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "textBox")]
            public void Dispose()
            {
                textBox.DisposeObject();
            }
        }
        #region fields
        

        char[] separatorChars = new char[] { '=', '\\', '/', ' ', '\t', '\r', '\n' };
        List<string> autoCompleteSource = new List<string>();
        bool highlightSelection = true;
        string[] previouslines = new string[0];
        int updateStack = 0;
        #endregion fields

        public event EventHandler<AutoCompleteSuggestionEventArgs> AutoCompleteGetSuggestions;
        //public event EventHandler<ColorSyntaxEventArg> ColorizeSyntax;

        #region constructors
        public ExtendedRichTextBox()
            : base()
        {
            HighlightColor = Color.Orange;
            
        }
        #endregion constructors

        #region properties
        public char[] SeparatorChars
        {
            get
            {
                return separatorChars;
            }
            set
            {
                Interlocked.Exchange(ref separatorChars, value);
            }
        }

        private bool AutoCompleteCaseSensitive
        {
            get;
            set;
        }
        private SelectionState CurrentSelectionState
        {
            get
            {
                return new SelectionState(SelectionStart, SelectionLength, highlightSelection);
            }
            set
            {
                if (value != null)
                {
                    char ch = 'a';
                    if (SelectionStart >= 0 && SelectionStart < Text.Length)
                        ch = Text[SelectionStart];

                    SelectionStart = value.SelectionStart;
                    SelectionLength = value.SelectionLength;
                    highlightSelection = value.HighlightSelection;
                }
            }
        }
        public Color HighlightColor
        {
            get;
            set;
        }
        #endregion properties

        #region scroll related
        public int HorizontalScrollPosition
        {
            get
            {
                return NativeMethods.GetScrollPos (Handle, System.Windows.Forms.Orientation.Horizontal);
            }
            set
            {
                NativeMethods.SetScrollPos(Handle, System.Windows.Forms.Orientation.Horizontal, value, true);
                NativeMethods.SendMessage((IntPtr)this.Handle, NativeMethods.WM_HSCROLL, (IntPtr)NativeMethods.SB_THUMBPOSITION + 0X10000 * value, (IntPtr)0);
                Invalidate();
            }
        }
        public int VerticalScrollPosition
        {
            get
            {
                return NativeMethods.GetScrollPos (Handle, System.Windows.Forms.Orientation.Vertical);
            }
            set
            {
                NativeMethods.SetScrollPos ( Handle , System.Windows.Forms.Orientation.Vertical, value, true );
                NativeMethods.SendMessage((IntPtr)this.Handle, NativeMethods.WM_VSCROLL, (IntPtr)NativeMethods.SB_THUMBPOSITION + 0X10000 * value, (IntPtr)0);
                Invalidate();
            }
        }
        #endregion scroll related

        #region update related
        private void UpdateLine(int lineId)
        {

        }
        private void UpdateState()
        {
            if (!UpdatesEnabled)
                return;
            BeginUpdate();
            RtfChanges changes = new RtfChanges(this);
            
            UpdateAutoComplete(changes);
            UpdateLinesColor(changes);
            changes.Apply();
            
            EndUpdate();
            previouslines = Lines;

        }
        protected bool UpdatesEnabled
        {
            get
            {
                return updateStack == 0;
            }
        }
        public void BeginUpdate()
        {
            if (Interlocked.Increment(ref updateStack) == 1)
                NativeMethods.SendMessage(this.Handle, NativeMethods.WM_SETREDRAW, (IntPtr)0, IntPtr.Zero);
        }
        public void EndUpdate()
        {
            if (Interlocked.Decrement(ref updateStack) == 0)
            {
                NativeMethods.SendMessage(this.Handle, NativeMethods.WM_SETREDRAW, (IntPtr)1, IntPtr.Zero);
                Invalidate();
            }
        }
        #endregion update related

        #region color related
        protected virtual Color? GetLineColor(string line)
        {
            return null;
        }
        private void UpdateLinesColor(RtfChanges changes)
        {
            changes.Apply();
            for (int i = 0; i < Lines.Length; i++)
            {
                if (previouslines.Length <= i || Lines[i] != previouslines[i])
                {
                    string currentLine = Lines[i];
                    Color? color = GetLineColor(currentLine);
                    if (color != null)
                    {
                        changes.ChangeLineColor(i, color.Value);
                    }
                }
            }
        }
        
        #endregion color related

        #region autocomplete related
        private bool AutoCompleted
        {
            get
            {
                if (SelectionLength > 0 && SelectionStart >= 0 && SelectionStart + SelectionLength <= Text.Length)
                {
                    string line = Lines[GetLineFromCharIndex(SelectionStart)];
                    return GetFirstCharIndexOfCurrentLine() + line.Length == SelectionStart + SelectionLength;
                }
                return false;
            }
        }
        private string GetAutoComplete(string line)
        {
            string suggestion = null;
            foreach (string existingText in autoCompleteSource)
            {
                if (existingText != line && existingText.StartsWith(line, AutoCompleteCaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase))
                {
                    suggestion = existingText.Substring(line.Length);
                    int pos = existingText.IndexOfAny(separatorChars, line.Length);
                    if (pos >= 0)
                        suggestion = existingText.Substring(line.Length, pos - line.Length);
                    break;
                }
            }
            if (suggestion == null)
            {
                foreach (string existingLine in Lines)
                {
                    if (existingLine != line && existingLine.StartsWith(line, AutoCompleteCaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase))
                    {
                        suggestion = existingLine.Substring(line.Length);
                        int pos = existingLine.IndexOfAny(separatorChars, line.Length);
                        if (pos >= 0)
                            suggestion = existingLine.Substring(line.Length, pos - line.Length);
                        break;
                    }
                }
            }

            suggestion = GetSuggestions(line, suggestion);
            return suggestion;
        }
        public void AddToAutoComplete(params string[] values)
        {
            foreach (var value in values)
            {
                if (!autoCompleteSource.Contains(value))
                    autoCompleteSource.Add(value);
            }
            autoCompleteSource.Sort();

        }
        private void UpdateAutoComplete(RtfChanges changes)
        {
            int lineId = GetLineFromCharIndex(SelectionStart);
            //do not auto complete if user is erasing or moving the cursor
            //if (lineId >= Lines.Length || (lineId < previouslines.Length && previouslines[lineId].StartsWith(Lines[lineId])))
            //    return; 
            if (lineId < 0 || lineId >= Lines.Length)
                return;
            string line = Lines[lineId];
            if (line.Length == 0)
                return;


            if ((GetFirstCharIndexOfCurrentLine() == SelectionStart - line.Length && SelectionLength == 0))
            {
                int start = SelectionStart;
                string autoComplete = GetAutoComplete(line);
                if (autoComplete != null)
                {
                    changes.AddTextInSelection(autoComplete);
                    //Text = string.Format("{0}{1}{2}", Text.Substring(0, SelectionStart), autoComplete, Text.Substring(SelectionStart));
                    Lines[lineId] = autoComplete;
                    Select(start, autoComplete.Length);
                }
            }
        }
        
        protected virtual string GetSuggestions(string line, string suggestion)
        {
            var handler = AutoCompleteGetSuggestions;
            if (handler != null)
            {
                var args = new AutoCompleteSuggestionEventArgs(line, suggestion);
                handler.Invoke(this, args);
                suggestion = args.Suggestion;
            }
            return suggestion;
        }
        #endregion autocomplete related

        protected virtual bool KeyAcceptsAutoComplete(Keys key)
        {
            return false;
        }
        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (AutoCompleted)
            {
                if ((e.KeyCode & Keys.Left) == Keys.Left)
                {
                }
                if (e.KeyCode == Keys.Delete)
                {
                    if (SelectionStart >= 0 && SelectionStart < Text.Length)
                    {
                        int selectionStart = SelectionStart;
                        Text = string.Format("{0}{1}", Text.Substring(0, SelectionStart), Text.Substring(SelectionStart + SelectionLength));
                        Select(selectionStart, 0);
                        e.Handled = true;
                    }
                }
                if (KeyAcceptsAutoComplete(e.KeyCode))
                {
                    SelectionStart = SelectionStart + SelectionLength;
                    e.Handled = true;
                }
                if (e.KeyCode == Keys.Enter)
                {
                    SelectionStart = SelectionStart + SelectionLength;
                }
                if (e.KeyCode == Keys.Back)
                {
                    if (SelectionStart == GetFirstCharIndexOfCurrentLine())
                    {
                    }
                    else
                    {
                        char ch = 'a';
                        if (SelectionStart >= 1 && SelectionStart < Text.Length)
                            ch = Text[SelectionStart - 1];
                        if (!separatorChars.Contains(ch))
                        {
                            int newSelectionStart = SelectionStart - 1;
                            int newSelectionLength = SelectionLength + 1;
                            Select(newSelectionStart, newSelectionLength);
                            Select(newSelectionStart, newSelectionLength);
                            e.Handled = true;
                        }
                        else
                        {
                            int newSelectionStart = SelectionStart - 1;
                            int newSelectionLength = SelectionLength + 1;
                            Select(newSelectionStart, newSelectionLength);
                        }
                    }
                }
            }
            base.OnKeyDown(e);
        }
        protected override void OnTextChanged(EventArgs e)
        {
            UpdateState();
            base.OnTextChanged(e);
            

        }


        public void Select(int start, int length, Color? foreColor, Color? backColor)
        {
            if (SelectionStart != start || SelectionLength != length)
                Select(start, length);
            if (foreColor != null && SelectionColor != foreColor.Value)
                SelectionColor = foreColor.Value;
            if (backColor != null && SelectionBackColor != backColor.Value)
                SelectionBackColor = backColor.Value;
        }
        
        
    }
}
